package com.ifonly.ar.core;

import com.ifonly.ar.exections.SqlExecuteException;
import com.ifonly.ar.helpers.ColumnWithValue;
import com.ifonly.ar.helpers.SqlInfo;
import com.ifonly.ar.sql.SqlExecutor;

import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * @author ifonly
 * @version 1.0 2015-12-03 20:47
 * @since JDK 1.6
 */
public abstract class ARModel implements Serializable {
    private static final long serialVersionUID = 3674160075081204636L;

    private Db db;

    private Table table;
    private String tableName;
    private String[] primaryKeys;
    private Map<String, Class<?>> columnAndTypes;

    private Map<String, Object> propertyAndValues = new HashMap<String, Object>();
    private Set<String> saveOrUpdateProperties = new HashSet<String>();

    {
        db = Db.getInstance();
        table = ARTableMapper.getInstance().get(this.getClass());
        tableName = table.getTableName();
        primaryKeys = table.getPrimaryKeys();
        columnAndTypes = table.getColumnAndTypes();
    }

    private ColumnWithValue[] getPrimaryKeyWithValue() {
        ColumnWithValue[] pkColumnWithValues = new ColumnWithValue[primaryKeys.length];
        for (int i = 0; i < primaryKeys.length; i++) {
            String pk = primaryKeys[i];
            ColumnWithValue cv = new ColumnWithValue();
            cv.column = pk;
            cv.value = propertyAndValues.get(pk);
            pkColumnWithValues[i] = cv;
        }
        return pkColumnWithValues;
    }

    public int save() {
        if (saveOrUpdateProperties.isEmpty()) {
            throw new IllegalArgumentException("Please set the value want to save");
        }
        SqlInfo sql = SqlBuilder.buildInsertSql(tableName, propertyAndValues, saveOrUpdateProperties);

        Connection conn = null;
        try {
            conn = db.getThreadLocalConnection();
            int i = SqlExecutor.insert(conn, sql.getSql(), sql.getParams());
            saveOrUpdateProperties.clear();
            return i;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                db.closeConnection(conn);
            } catch (SQLException e) {
                //
            }
        }
    }

    public ColumnWithValue[] saveReturnGeneratedKeys() {
        if (primaryKeys == null || primaryKeys.length == 0) {
            throw new IllegalArgumentException("the table[" + tableName + "] has no primary key");
        }
        if (saveOrUpdateProperties.isEmpty()) {
            throw new IllegalArgumentException("Please set the value want to save");
        }
        SqlInfo sql = SqlBuilder.buildInsertSql(tableName, propertyAndValues, saveOrUpdateProperties);

        Connection conn = null;
        try {
            conn = db.getThreadLocalConnection();
            ResultSet rs = SqlExecutor.insertReturnGeneratedKeys(conn, primaryKeys, sql.getSql(), sql.getParams());
            int length = primaryKeys.length;
            ColumnWithValue[] columnWithValues = new ColumnWithValue[length];

            for (int i = 0; i < length; i++) {
                String pk = primaryKeys[i];
                Object value = null;
                if (rs.next()) {
                    value = rs.getObject(1);
                }
                columnWithValues[i] = new ColumnWithValue(pk, value);
            }
            saveOrUpdateProperties.clear();
            return columnWithValues;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new SqlExecuteException("execute sql failed", e);
        } finally {
            try {
                db.closeConnection(conn);
            } catch (SQLException e) {
                //
            }
        }
    }

    public int update() {
        if (saveOrUpdateProperties.isEmpty()) {
            throw new IllegalArgumentException("Please set the value want to save");
        }
        ColumnWithValue[] primaryKeyWithValues = getPrimaryKeyWithValue();
        Connection connection = null;
        try {
            SqlInfo sql = SqlBuilder.buildUpdateSql(tableName, propertyAndValues, saveOrUpdateProperties, primaryKeyWithValues);
            connection = db.getThreadLocalConnection();
            int i = SqlExecutor.update(connection, sql.getSql(), sql.getParams());
            saveOrUpdateProperties.clear();
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new SqlExecuteException("execute sql failed", e);
        } finally {
            try {
                db.closeConnection(connection);
            } catch (SQLException e) {
                //
            }
        }
    }

    public int delete() {
        ColumnWithValue[] primaryKeyWithValues = getPrimaryKeyWithValue();
        Connection connection = null;
        try {
            SqlInfo sql = SqlBuilder.buildDeleteSql(tableName, primaryKeyWithValues);
            connection = db.getThreadLocalConnection();
            int i = SqlExecutor.delete(connection, sql.getSql(), sql.getParams());
            saveOrUpdateProperties.clear();
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
            throw new SqlExecuteException("execute sql failed", e);
        } finally {
            try {
                db.closeConnection(connection);
            } catch (SQLException e) {
                //
            }
        }
    }

    private static ARModel toARModel(Record record, Class<? extends ARModel> clazz) {
        try {
            ARModel model = clazz.newInstance();
            if (record == null) {
                return null;
            }
            model.propertyAndValues = record.getColumnAndValues();
            return model;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static ARModel findByPrimaryKeys(Class<? extends ARModel> clazz, ColumnWithValue[] primaryKeys) {
        Record record = SqlExecutor.findByPrimaryKeys(clazz, primaryKeys);
        return toARModel(record, clazz);
    }

    public static ARModel findByPrimaryKeys(Class<? extends ARModel> clazz, Object...primaryKeys) {
        Record record = SqlExecutor.findByPrimaryKeys(clazz, primaryKeys);
        return toARModel(record, clazz);
    }

    /*
    public static ARModel findByIds(Object...ids) {
        return null;
    }

    public static ARModel findById(Object id) {
        return null;
    }

    public static ARModel list() {
        return null;
    }

    public static ARModel list(String...columns) {
        return null;
    }

    public static Pagination<? extends ARModel> pagination(int pageNo, int pageSize) {
        return null;
    }

    public static Pagination<? extends ARModel> pagination(int pageNo, int pageSize, String...columns) {
        return null;
    }*/

    public void set(String property, Object value) {
        if (!columnAndTypes.containsKey(property)) {
            throw new IllegalArgumentException("The Table[" + tableName + "] not have the column[" + property + "]");
        }

        propertyAndValues.put(property, value);
        saveOrUpdateProperties.add(property);
    }

    @Override
    public String toString() {
        return "ARModel{" +
                "tableName='" + tableName + '\'' +
                ", primaryKeys=" + Arrays.toString(primaryKeys) +
                ", propertyAndValues=" + propertyAndValues +
                '}';
    }
}
